﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
#if NETFX_CORE
using System.Threading.Tasks;
#endif
using Microsoft.VisualStudio.TestPlatform.UnitTestFramework;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MugenInjection;
using MugenInjection.Interface;
using MugenInjection.Scope;
using TestModels.Class;
using Should;

namespace MugenInjectionTest.Core
{
    [TestClass]
    public class ScopeLifecycleTest : TestBase
    {
        [TestMethod]
        public void SingletonScopeLifecycleTest()
        {
            const int countThread = 1000;
            var locker = new object();
            using (var injector = GetInjector())
            {
                var blockedCollection = new List<Simple>();
                injector.Bind<Simple>().ToSelf().InSingletonScope();
                var simple = injector.Get<Simple>();

                var simple1 = injector.Get<Simple>();
                simple.ShouldEqual(simple1);

                ThreadInvoker.Invoke(() =>
                {
                    var simple2 = injector.Get<Simple>();
                    lock (locker)
                    {
                        blockedCollection.Add(simple2);
                    }
                }, countThread);

                blockedCollection.Count.ShouldEqual(countThread);
                blockedCollection.All(simple2 => simple2 == simple).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void TransientScopeLifecycleTest()
        {
            const int countThread = 1000;
            var locker = new object();
            using (var injector = GetInjector())
            {
                var blockedCollection = new List<Simple>();
                injector.Bind<Simple>().ToSelf().InTransientScope();

                var simple = injector.Get<Simple>();
                var simple1 = injector.Get<Simple>();
                simple.ShouldNotEqual(simple1);

                ThreadInvoker.Invoke(() =>
                {
                    var simple2 = injector.Get<Simple>();
                    lock (locker)
                    {
                        blockedCollection.Add(simple2);
                    }
                }, countThread);

                blockedCollection.Count.ShouldEqual(countThread);
                for (int index = 0; index < blockedCollection.Count; index++)
                {
                    var simple2 = blockedCollection[index];
                    var simples = blockedCollection.ToList();
                    simples.Remove(simple2);
                    simples.Any(c => c.Equals(simple2)).ShouldBeFalse();
                }
            }
        }

        [TestMethod]
        public void ThreadScopeLifecycleTest()
        {
            const int countThread = 1000;
            var locker = new object();
            using (var injector = GetInjector())
            {
                var dictionary = new Dictionary<int, List<Simple>>();
                injector.Bind<Simple>().ToSelf().InThreadScope();
                ThreadInvoker.Invoke(() =>
                {
                    var simple = injector.Get<Simple>();
                    lock (locker)
                    {

#if NETFX_CORE
                        int threadId = Environment.CurrentManagedThreadId;
#else
                        int threadId = Thread.CurrentThread.ManagedThreadId;
#endif
                        List<Simple> values;
                        dictionary.TryGetValue(threadId, out values);
                        if (values == null)
                        {
                            values = new List<Simple>();
                            dictionary[threadId] = values;
                        }
                        values.Add(simple);
                    }
                }, countThread);
                dictionary.SelectMany(pair => pair.Value).Count().ShouldEqual(countThread);
                foreach (var pair in dictionary)
                    pair.Value.All(simple => simple.Equals(pair.Value[0])).ShouldBeTrue();
            }
        }

        [TestMethod]
        public void UnitOfWorkScopeLifecycleTest()
        {
            const int countThread = 1000;
            var locker = new object();
            using (var injector = GetInjector())
            {
                using (var child = (MugenInjector)injector.CreateChild())
                {
                    var blockedCollection = new List<Simple>();
                    var blockedCollectionChild = new List<Simple>();
                    injector.Bind<Simple>().ToSelf().InUnitOfWorkScope();

                    var simple = injector.Get<Simple>();
                    var simple1 = injector.Get<Simple>();
                    simple.ShouldEqual(simple1);

                    var childSimple = child.Get<Simple>();
                    var childSimple1 = child.Get<Simple>();
                    childSimple.ShouldEqual(childSimple1);

                    simple.ShouldNotEqual(childSimple);
                    ThreadInvoker.Invoke(() =>
                    {
                        var simple2 = injector.Get<Simple>();
                        var childSimple2 = child.Get<Simple>();
                        lock (locker)
                        {
                            blockedCollection.Add(simple2);
                            blockedCollectionChild.Add(childSimple2);
                        }
                    }, countThread);
                    blockedCollection.Count.ShouldEqual(countThread);
                    blockedCollectionChild.Count.ShouldEqual(countThread);
                    foreach (var simple2 in blockedCollection)
                    {
                        simple.ShouldEqual(simple2);
                        blockedCollectionChild.Any(c => c.Equals(simple2)).ShouldBeFalse();
                    }
                    foreach (var simple2 in blockedCollectionChild)
                    {
                        childSimple.ShouldEqual(simple2);
                        blockedCollection.Any(c => c.Equals(simple2)).ShouldBeFalse();
                    }
                }
            }
        }

#if !NET2
        [TestMethod]
        public void ManagedScopeLifecycleCorrectWorkTest()
        {
            using (var injector = GetInjector())
            {
                injector.Bind<IDisposable>().To<ManagedScopeObject>().InManagedScope().TryDisposeObjects();

                TestUtils.Catch<InvalidOperationException>(() => injector.Get<IDisposable>());

                IDisposable managedScopeObject, managedScopeObject1;
                using (var scope = injector.CreateManagedScope())
                {
                    managedScopeObject = scope.Get<IDisposable>();
                    managedScopeObject1 = scope.Get<IDisposable>();
                    managedScopeObject.ShouldEqual(managedScopeObject1);

                    var managedScope = injector.CreateManagedScope();

                    var managedScopeObject2 = managedScope.Get<IDisposable>();
                    var managedScopeObject3 = managedScope.Get<IDisposable>();
                    managedScopeObject2.ShouldEqual(managedScopeObject3);
                    managedScope.ShouldNotEqual(managedScopeObject2);

                    managedScope.Dispose();

                    ((ManagedScopeObject)managedScopeObject2).IsDisposed.ShouldBeTrue();
                    ((ManagedScopeObject)managedScopeObject3).IsDisposed.ShouldBeTrue();
                    TestUtils.Catch<ObjectDisposedException>(() => ManagedScopeLifecycle.SetManagedScope(managedScope));
                }

                ((ManagedScopeObject)managedScopeObject).IsDisposed.ShouldBeTrue();
                ((ManagedScopeObject)managedScopeObject1).IsDisposed.ShouldBeTrue();
            }
        }

        [TestMethod]
        public void ManagedScopeLifecycleManyThreadTest()
        {
            const int countThread = 1000;
            var locker = new object();
            using (var injector = GetInjector())
            {
                var blockedCollectionScope1 = new List<Simple>();
                var blockedCollectionScope2 = new List<Simple>();
                injector.Bind<Simple>().ToSelf().InManagedScope();
                var managedScope = injector.CreateManagedScope();
                var simple = managedScope.Get<Simple>();
                var managedScope1 = injector.CreateManagedScope();
                var simpleScope2 = managedScope1.Get<Simple>();

                simple.ShouldNotEqual(simpleScope2);
                ThreadInvoker.Invoke(() =>
                {
                    var simple1 = managedScope.Get<Simple>();
                    var simple2 = managedScope1.Get<Simple>();
                    simple1.ShouldNotEqual(simple2);
                    lock (locker)
                    {
                        blockedCollectionScope1.Add(simple1);
                        blockedCollectionScope2.Add(simple2);
                    }
                }, countThread);
                blockedCollectionScope1.Count.ShouldEqual(countThread);
                blockedCollectionScope2.Count.ShouldEqual(countThread);
                foreach (var obj in blockedCollectionScope1)
                {
                    simple.ShouldEqual(obj);
                    blockedCollectionScope2.Any(c => c.Equals(obj)).ShouldBeFalse();
                }
                foreach (var obj in blockedCollectionScope2)
                {
                    simpleScope2.ShouldEqual(obj);
                    blockedCollectionScope1.Any(c => c.Equals(obj)).ShouldBeFalse();
                }
            }
        }
#endif


        [TestMethod]
        public void WeakReferenceScopeLifecycleTest()
        {
            using (var injector = GetInjector())
            {
                injector.Bind<Simple>().ToSelf().InWeakReferenceScope();
                var simple = GetWeakId(injector);
                var simple2 = GetWeakId(injector);
                simple.ShouldEqual(simple2);
                GC.Collect();
                GC.WaitForPendingFinalizers();
                GC.Collect();

                var simple3 = GetWeakId(injector);
                var simple4 = GetWeakId(injector);
                simple3.ShouldEqual(simple4);
                simple.ShouldNotEqual(simple3);
            }
        }

        private static Guid GetWeakId(MugenInjector injector)
        {
            return injector.Get<Simple>().Id;
        }
    }

    public static class ThreadInvoker
    {
        public static void Invoke(Action action, int countThread)
        {
            var listThreads = new List<WaitHandle>();
            for (int i = 0; i < countThread; i++)
            {
                var reset = new ManualResetEvent(false);
                InvokeAsync(() =>
                                {
                                    action();
                                    reset.Set();
                                });
                listThreads.Add(reset);
            }
            for (int i = 0; i < countThread; i++)
                listThreads[i].WaitOne();
        }

        public static EventWaitHandle InvokeAsync(Action action)
        {
            var reset = new ManualResetEvent(false);
#if NETFX_CORE
            Windows.System.Threading.ThreadPool.RunAsync(state =>
                                             {
                                                 action();
                                                 reset.Set();
                                             });
#else
            ThreadPool.QueueUserWorkItem(state =>
                                             {
                                                 action();
                                                 reset.Set();
                                             });
#endif
            return reset;
        }
    }

    public class ManagedScopeObject : IDisposable
    {
        #region Property

        public bool IsDisposed { get; private set; }

        #endregion

        #region Implementation of IDisposable

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        /// <filterpriority>2</filterpriority>
        public void Dispose()
        {
            IsDisposed = true;
        }

        #endregion
    }
}
